__all__ = ['dump', 'dumps', 'load', 'loads', 'Bon']
from struct import pack, unpack

# 0 - False, 8 bit
# 1 - True, 8 bit
# c - char, 8 + 8 bit = 16 bit
# C - uchar, 8 + 8 bit = 16 bit
# b - byte, 8 + 8 bit = 16 bit
# B - ubyte, 8 + 8 bit = 16 bit
# h - short, 8 + 16 bit = 24 bit
# H - ushort, 8 + 16 bit = 24 bit
# i - int, 8 + 32 bit = 40 bit
# I - uint, 8 + 32 bit = 40 bit
# l - long, 8 + 64 bit = 72 bit
# L - ulong, 8 + 64 bit = 72 bit
# f - float, 8 + 32 bit = 40 bit
# d - double, 8 + 64 bit = 72 bit
# s - string, 8 bit + ...
# u - unicode string, 8 bit + ...
# ( - tuple, 8 bit + ...
# [ - list, 8 bit + ...
# { - dict, 8 bit + ...

def dump(value, file):
	buf = dumps(value)
	file.write(buf)

def load(file):
	buf = file.read()
	return loads(buf)

def dumps(obj):
	buf = serialize(obj)
	_buf = serialize(len(buf))
	return _buf + buf

def loads(buf):
	obj_size, s, e = deserialize(buf, 0, len(buf))
	obj, _, _ = deserialize(buf, e, len(buf))
	return obj

def serialize(obj):
	buflist = []
	
	if obj is None:
		buflist.append('n')
	
	elif type(obj) == bool:
		buflist.append('1' if obj else '0')
	
	elif type(obj) in (int, long):
		if obj >= 0:
			if obj < 2 ** 8:
				buflist.append('B')
				buflist.append(pack('!B', obj))
			elif obj < 2 ** 16:
				buflist.append('H')
				buflist.append(pack('!H', obj))
			elif obj < 2 ** 32:
				buflist.append('I')
				buflist.append(pack('!I', obj))
			elif obj < 2 ** 64:
				buflist.append('L')
				buflist.append(pack('!Q', obj))
			else:
				raise Exception, 'requires %d <= signed number <= %d' % (0, 2 ** 64 - 1)
		else:
			if -(2 ** 7) <= obj < 2 ** 7:
				buflist.append('b')
				buflist.append(pack('!b', obj))
			elif -(2 ** 15) <= obj < 2 ** 15:
				buflist.append('h')
				buflist.append(pack('!h', obj))
			elif -(2 ** 31) <= obj < 2 ** 31:
				buflist.append('i')
				buflist.append(pack('!i', obj))
			elif -(2 ** 63) <= obj < 2 ** 63:
				buflist.append('l')
				buflist.append(pack('!q', obj))
			else:
				raise Exception, 'requires %d <= unsigned number <= %d' % (-(2 ** 63), 2 ** 63 - 1)
	
	elif type(obj) == float:
		buflist.append('d')
		buflist.append(pack('!d', obj))
	
	elif type(obj) == str:
		if len(obj) == 1:
			buflist.append('C')
			buflist.append(obj)
		else:
			buflist.append('s')
			buflist.append(serialize(len(obj)))
			buflist.append(obj)
				
	elif type(obj) == unicode:
		buflist.append('u')
		buffer_obj = buffer(obj)
		buflist.append(serialize(len(buffer_obj)))
		buflist.append(str(buffer_obj))
		
	elif type(obj) == tuple:
		buflist.append('(')
		buflist.append(serialize(len(obj)))
		for _obj in obj:
			buflist.append(serialize(_obj))
	
	elif type(obj) == list:
		buflist.append('[')
		buflist.append(serialize(len(obj)))
		for _obj in obj:
			buflist.append(serialize(_obj))
	
	elif type(obj) == dict:
		buflist.append('{')
		buflist.append(serialize(len(obj)))
		for _obj_k, _obj_v in obj.iteritems():
			buflist.append(serialize(_obj_k))
			buflist.append(serialize(_obj_v))
	
	return ''.join(buflist)

def deserialize(buf, s, e):
	obj_type = unpack('!c', buf[s])[0]
	
	if obj_type == 'n':
		obj = None
		return obj, s, s + 1
	
	elif obj_type == '0':
		obj = False
		return obj, s, s + 1
	
	elif obj_type == '1':
		obj = True
		return obj, s, s + 1
	
	elif obj_type == 'c':
		obj = chr(256 + unpack('!b', buf[s + 1:s + 2])[0])
		return obj, s, s + 2
	
	elif obj_type == 'C':
		obj = chr(unpack('!B', buf[s + 1:s + 2])[0])
		return obj, s, s + 2
	
	elif obj_type == 'b':
		obj = unpack('!b', buf[s + 1:s + 2])[0]
		return obj, s, s + 2
	
	elif obj_type == 'B':
		obj = unpack('!B', buf[s + 1:s + 2])[0]
		return obj, s, s + 2
	
	elif obj_type == 'h':
		obj = unpack('!h', buf[s + 1:s + 3])[0]
		return obj, s, s + 3
	
	elif obj_type == 'H':
		obj = unpack('!H', buf[s + 1:s + 3])[0]
		return obj, s, s + 3
	
	elif obj_type == 'i':
		obj = unpack('!i', buf[s + 1:s + 5])[0]
		return obj, s, s + 5
	
	elif obj_type == 'I':
		obj = unpack('!I', buf[s + 1:s + 5])[0]
		return obj, s, s + 5
	
	elif obj_type == 'l':
		obj = unpack('!q', buf[s + 1:s + 9])[0]
		return obj, s, s + 9
	
	elif obj_type == 'L':
		obj = unpack('!Q', buf[s + 1:s + 9])[0]
		return obj, s, s + 9
	
	elif obj_type == 'f':
		obj = unpack('!f', buf[s + 1:s + 5])[0]
		return obj, s, s + 5
	
	elif obj_type == 'd':
		obj = unpack('!d', buf[s + 1:s + 9])[0]
		return obj, s, s + 9
	
	elif obj_type == 's':
		obj_size, _, _s = deserialize(buf, s + 1, e)
		obj = buf[_s:_s + obj_size]
		return obj, s, _s + obj_size
			
	elif obj_type == 'u':
		obj_size, _, _s = deserialize(buf, s + 1, e)
		obj = buf[_s:_s + obj_size].decode('utf-16')
		return obj, s, _s + obj_size
	
	elif obj_type == '(':
		obj = []
		obj_size, _, _s = deserialize(buf, s + 1, e)
		for i in range(obj_size):
			_obj, _, _s = deserialize(buf, _s, e)
			obj.append(_obj)
		return tuple(obj), s, _s
	
	elif obj_type == '[':
		obj = []
		obj_size, _, _s = deserialize(buf, s + 1, e)
		for i in range(obj_size):
			_obj, _, _s = deserialize(buf, _s, e)
			obj.append(_obj)
		return obj, s, _s
	
	elif obj_type == '{':
		obj = {}
		obj_size, _, _s = deserialize(buf, s + 1, e)
		for i in range(obj_size):
			_obj_k, _, _s = deserialize(buf, _s, e)
			_obj_v, _, _s = deserialize(buf, _s, e)
			obj[_obj_k] = _obj_v
		return obj, s, _s

class Bon(object):
	def __init__(self, f):
		self.f = f
	
	def dump(self, obj):
		return dump(obj, self.f)
	
	def load(self):
		return load(self.f)
	
if __name__ == '__main__':
	a = {0: [None, True, False, -(2 ** 8), 2 ** 16 - 1, 2 ** 32 - 1, -0.12345, 'a', 'abcde'],
		1: [(u'\xee\xff', 'qwerty', {})], 2:None, 3:False, 4:True, 5:-0.12345678901234567890}
	
	print 'bon: serialized in', len(dumps(a)), 'bytes:', repr(dumps(a)), '\n'
	print 'bon: deserialized', loads(dumps(a)), '\n'
	
	import marshal
	print 'marshal: serialized in', len(marshal.dumps(a)), 'bytes:', repr(marshal.dumps(a)), '\n'
	
	import pickle
	print 'pickle: serialized in', len(pickle.dumps(a)), 'bytes:', repr(pickle.dumps(a)), '\n'
	
